package main

import (
	"bufio"
	"errors"
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/howeyc/gopass"
	"github.com/jmoiron/sqlx"
	"github.com/spf13/viper"
	"github.com/urfave/cli/v2"
	"log"
	"os"
	"strings"
)

//构建命令使用说明 所有常量都在 6_config.go中
func usage() {
	app.Name = projectName //项目名称
	app.Authors = []*cli.Author{{
		Name:  author,
		Email: authorEmail,
	}}
	app.Version = version                     //版本号
	app.Copyright = "@Copyright " + copyright //版权保护
	app.Usage = "快速生成用于操作MYSQL.etc的CURD和文档"   //说明
	app.Commands = []*cli.Command{
		{
			Name:    "help",
			Aliases: []string{"h", "?"},
			Usage:   "show help",
			Action: func(c *cli.Context) error {
				_ = cli.ShowAppHelp(c)
				return nil
			},
		},
		{
			Name:    "version",
			Aliases: []string{"v"},
			Usage:   "print the version",
			Action: func(c *cli.Context) error {
				cli.ShowVersion(c)
				return nil
			},
		},
	}
	app.HideVersion = true
	app.HideHelp = true
	// 设置 Flag 在这里新增命令 Flag
	app.Flags = []cli.Flag{
		&cli.StringFlag{Name: "t", Value: "mysql", Usage: "数据库驱动类型，例如:mysql,postgreSQL..."}, // 数据库驱动类型
		&cli.StringFlag{Name: "h", Value: "127.0.0.1", Usage: "Database address"},           // 数据库地址
		&cli.IntFlag{Name: "P", Value: 3306, Usage: "port number"},                          // 端口号
		&cli.StringFlag{Name: "u", Value: "root", Usage: "database username", Required: true},
		&cli.StringFlag{Name: "p", Value: "root", Usage: "database password", Required: true, DefaultText: ""},
		&cli.StringFlag{Name: "c", Value: "./config.toml", Usage: "configuration file path"}, //配置文件地址
		&cli.StringFlag{Name: "d", Usage: "database name"},                                   //具体的数据库
		&cli.StringFlag{Name: "new", Usage: "project name"},                                  //新建一个模版项目
	}
}

// start 命令行实现，从终端读取数据库配置信息，并将信息保存到 Config 结构体。
func start() {
	usage()
	app.Action = func(c *cli.Context) error {
		var file string
		if path := c.String("c"); path != "" {
			file = path
		} else {
			dbc.DriverName = c.String("t") // 数据库驱动名
			dbc.Host = c.String("h")       // 数据库地址
			dbc.Port = c.Int("P")          // 端口号
			dbc.Username = c.String("u")   // 数据库用户名称
			dbc.Password = c.String("p")   // 密码
			if c.NumFlags() > 0 {
				dbName := c.String("d") // 数据库名称
				if dbName == "" {
					return cli.Exit("数据库为空，请使用 -d 参数", 9)
				}
				dbc.DBName = dbName
				if dbc.Password == "" {
					pass, err := gopass.GetPasswdPrompt("输入密码:", false, os.Stdin, os.Stdout)
					if err != nil {
						return err
					}
					dbc.Password = string(pass)
					clean() // 该函数在 5_tool.go中
				}
				// 执行命令
				if err := cmd(); err != nil {
					return cli.Exit(err, 1)
				}
			} else {
				file = "config.toml"
				goto Read
				return nil
			}
		}
	Read:
		path, _ := os.Getwd()
		viper.AddConfigPath(path + file)
		viper.SetConfigFile(file)
		//viper.SetConfigType("json")
		viper.SetConfigType("toml")
		//viper.SetConfigType("yaml")
		if err := viper.ReadInConfig(); err != nil {
			panic(fmt.Errorf("Fatal error config file: %s \n", err))
		}
		viper.WatchConfig()
		viper.OnConfigChange(func(e fsnotify.Event) {
			fmt.Println("config file changed:", e.Name)
			if err := viper.Unmarshal(&cfg); err != nil {
				fmt.Println(err)
			}
		})
		if err := viper.Unmarshal(&cfg); err != nil {
			fmt.Println(err)
		}
		// 执行命令
		if err := cmd(); err != nil {
			return cli.Exit(err, 1)
		}
		return nil
	}
	if err := app.Run(os.Args); err != nil {
		log.Fatal("RUN>>", err)
	}
}

// Command 实现命令功能，关键是看 Command() 里面的 Handlers() 方法，该方法才是所有命令的入口。
func cmd() error {
	var (
		db  *sqlx.DB
		err error
	)
	if dbc != nil {
		db, err = newDB(dbc)
	} else {
		db, err = initDB(cfg) // 输入数据配置信息，连接数据库。
	}
	if db == nil || err != nil {
		return errors.New("database connect failed>>" + err.Error())
	}
	//初使工作
	var dbn string
	if cfg.Gen.DbName != "" {
		dbn = cfg.Gen.DbName
	} else {
		dbn = dbc.DBName
	}
	obj := &schema{ // 获取 *Schema 对象
		DB:      db,
		dBName:  dbn, // 初始化数据库名。
		convert: mysqlTypeToGoType,
	}
	obj.path = obj.getExeRootDir() + "output" + "/" //默认当前命令所在目录，GetExeRootDir()在 4_tool.go中。
	var tagKeys string
	if cfg.Gen.DSN != "" {
		tagKeys = cfg.Gen.TagKey
		if tagKeys == "" {
			tagKeys = "json"
		}
	} else {
		tagKeys = dbc.TagKey
		if tagKeys == "" {
			tagKeys = "json"
		}
	}
	obj.tagKeys = obj.checkCharDoSpecial(tagKeys, ',', `[\w\,\-]+`)
	obj.getAllTable() // 执行完这个方法后，Schema.Tables中就保存了当前数据库中所有的表名，和其对应的注释信息。
	cm := command{
		l: obj,
	}
	cm.Help(nil)
	handlers := cm.Handlers() // 重点看这个方法，该方法才是所有命令的入口。
	// 从终端获取输入参数，并将输入参数映射到具体的命令。
	// 处理命令的就是 8_commands.go 文件中的 Handlers() 方法，以下逻辑的重点就在 Handlers() 方法。
	// 下面的代码无需改动，如果要增加或减少一条命令，只需在 command.Handlers() 中新增或减少一条映射即可!
	br := bufio.NewReader(os.Stdin)
	for {
		fmt.Print("-> # ")
		line, _, _ := br.ReadLine()
		if len(line) == 0 {
			continue
		}
		tokens := strings.Split(string(line), " ")
		if handler, ok := handlers[strings.ToLower(tokens[0])]; ok {
			ret := handler(tokens)
			if ret != 0 {
				break
			}
		} else {
			fmt.Println("未知的命令>>", tokens[0])
		}
	}
	return nil
}
